package com.hermeneute.fsa;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FSTAutomaton {
	int start;
	HashSet finalStates;
	HashSet sinkStates;
	int firstFreeState;
	Hashtable transitionTable;
	static int autcreated;
	
	public FSTAutomaton() {
		autcreated++;
		setFirstFreeState (0);
		finalStates = new HashSet();
		transitionTable = new Hashtable();
		sinkStates = new HashSet ();
		setFirstFreeState(2);
	}
	
	public FSTAutomaton(FSTAutomaton a) {
		autcreated++;
		finalStates = new HashSet();
		transitionTable = new Hashtable();
		sinkStates = new HashSet ();
		setStart (a.getStart());
		setFirstFreeState (a.getFirstFreeState ());
		for (Iterator ifs = a.getFinalStates();ifs.hasNext();){
			insertFinalState(ifs.next());
		}
		for(Enumeration e = a.getStates();e.hasMoreElements();) {
			Integer is1 = (Integer)e.nextElement();
			//System.out.println("state "+is1);
			for(FSTTransitionIterator it=a.getTransitions(is1);it.hasNext();) {
				it.next();
				// 		System.out.println("addTransition ("+is1+","+
				// 				   it.getEvent().getIn()
				// 				   +":"+it.getEvent().getOut()
				// 				   +","+it.getState()+")");
				addTransition(is1.intValue(),
						it.getEvent(),
						it.getState());
			}
		}
		//System.out.println ("Done ");
	}
	
	FSTTransitionIterator getTransitions(int st) {
		Integer is = new Integer(st);
		if (transitionTable.get(is) == null) {
			return new FSTTransitionIterator (null);
		} else {
			return ((FSTTransitionList)transitionTable.get(is)).
			getTransitions();
		}
	}
	
	FSTTransitionIterator getTransitions(Integer is) {
		if (transitionTable.get(is) == null) {
			return new FSTTransitionIterator (null);
		} else {
			return ((FSTTransitionList)transitionTable.get(is)).
			getTransitions();
		}
	}
	
	void addTransition(int st1,FSTEvent e,int st2){
		Integer is1 = new Integer(st1);
		if (transitionTable.get(is1) == null) {
			transitionTable.put(is1, new FSTTransitionList());
			((FSTTransitionList)transitionTable.get(is1)).
			addTransition(new FSTTransition(e,st2));
		}else{
			((FSTTransitionList)transitionTable.get(is1)).
			addTransition(new FSTTransition(e,st2));
		}
	}
	
	void replaceTransition(int st1,FSTEvent e,int st2){
		Integer is1 = new Integer(st1);
		if (transitionTable.get(is1) == null) {
			transitionTable.put(is1, new FSTTransitionList());
			((FSTTransitionList)transitionTable.get(is1)).
			addTransition(new FSTTransition(e,st2));
		}else{
			((FSTTransitionList)transitionTable.get(is1)).
			replaceTransition(new FSTTransition(e,st2));
		}
	}
	
	void deleteTransition(int st1,FSTEvent e){
		Integer is1 = new Integer(st1);
		if (transitionTable.get(is1) != null) {
			((FSTTransitionList)transitionTable.get(is1)).
			removeTransition(new FSTTransition(e,0));
		}
	}
	void deleteState(int st){
		transitionTable.remove(new Integer(st));
	}
	void deleteState(Integer ist){
		transitionTable.remove((Object)ist);
	}
	void deleteState(Object ost){
		transitionTable.remove(ost);
	}
	
	FSTTransitionList getTransitionList(int st){
		return (FSTTransitionList)transitionTable.get(new Integer(st));
	}
	
	FSTTransitionList getTransitionList(Integer ist){
		return (FSTTransitionList)transitionTable.get(ist);
	}
	
	Enumeration getStates(){
		return transitionTable.keys();
	}
	
	Iterator getFinalStates(){
		return finalStates.iterator();
	}
	
	void setStart(int st){
		start = st;
	}
	int getStart() {
		return start;
	}
	void setFirstFreeState(int st){
		firstFreeState = st;
	}
	int getFirstFreeState() {
		return firstFreeState;
	}
	
	void insertFinalState(int st) {
		finalStates.add(new Integer (st));
	}
	void insertFinalState(Integer ist) {
		finalStates.add(ist);
	}
	void insertFinalState(Object ost) {
		finalStates.add((Integer)ost);
	}
	
	void deleteFinalState() {
		finalStates.clear();
	}
	void deleteFinalState(int st) {
		finalStates.remove(new Integer(st));
	}
	void deleteFinalState(Integer ist) {
		finalStates.remove(ist);
	}
	void deleteFinalState(Object ost) {
		finalStates.remove(ost);
	}
	
	boolean isFinalState(int st) {
		return finalStates.contains((Object)new Integer(st));
	}
	boolean isFinalState(Integer ist) {
		return finalStates.contains((Object)ist);
	}
	boolean isFinalState(Object ost) {
		return finalStates.contains(ost);
	}
	
	void insertSinkState(int st) {
		sinkStates.add(new Integer (st));
	}
	void insertSinkState(Integer ist) {
		sinkStates.add(ist);
	}
	void insertSinkState(Object ost) {
		sinkStates.add((Integer)ost);
	}
	void deleteSinkState(int st) {
		sinkStates.remove((Object)new Integer(st));
	}
	void deleteSinkState(Integer ist) {
		sinkStates.remove((Object)ist);
	}
	void deleteSinkState(Object ost) {
		sinkStates.remove(ost);
	}
	
	boolean isSinkState(int st) {
		return sinkStates.contains((Object)new Integer(st));
	}
	boolean isSinkState(Integer ist) {
		return sinkStates.contains((Object)ist);
	}
	boolean isSinkState(Object ost) {
		return sinkStates.contains(ost);
	}
	
	boolean exists(int s1, FSTEvent e, int s2) {
		FSTTransitionList tl = getTransitionList(s1);
		if (tl != null) {
			return tl.exists(new FSTTransition(e,s2));
		}else{
			return false;
		}
	}
	
	int nextState(int s,FSTEvent e) {
		FSTTransitionList tl = getTransitionList(s);
		FSTTransition tr = null;
		if (tl != null ) {
			tr = tl.get(new FSTTransition(e,0));
			if (tr == null && e.getIn() != FSTEvent.Epsilon) {
				tr = 
					tl.get(new FSTTransition(
							new FSTEvent(e.getIn(),FSTEvent.Any),0));
				if (tr == null) {
					tr = 
						tl.get(new FSTTransition(
								new FSTEvent(FSTEvent.Any,e.getOut()),0));
					if (tr == null) {
						tr = 
							tl.get(new FSTTransition(
									new FSTEvent(FSTEvent.Any,FSTEvent.Any),0));
					}
				}
			}
		}
		if (tr == null) {
			return 0;
		}else{
			return tr.getState();
		}
	}
	int nextState1(int s,FSTEvent e) {
		FSTTransitionList tl = getTransitionList(s);
		FSTTransition tr = null;
		if (tl != null ) {
			tr = tl.get(new FSTTransition(e,0));
		}
		if (tr == null) {
			return 0;
		}else{
			return tr.getState();
		}
	}
	
	
	char getChar(String str){
		if (str.charAt(0) == '\\'){
			if (str.charAt(1) == '1') {
				return (char)1;
			}else if (str.charAt(1) == '2') {
				return (char) 2;
			}else if (str.charAt(1) == '2') {
				return (char) 2;
			}else if (str.charAt(1) == '3') {
				return (char) 3;
			}else if (str.charAt(1) == '4') {
				return (char) 4;
			}else if (str.charAt(1) == '5') {
				return (char) 5;
			}else if (str.charAt(1) == '6') {
				return (char) 6;
			}else if (str.charAt(1) == '7') {
				return (char) 7;
			}else if (str.charAt(1) == '8') {
				return (char) 8;
			}else if (str.charAt(1) == '\\') {
				return str.charAt(1);
			}else if (str.charAt(1) == 'b') {
				return ' ';
			}else if (str.charAt(1) == 't') {
				return '\t';
			}else if (str.charAt(1) == 'n') {
				return '\n';
			}else if (str.charAt(1) == 'r') {
				return '\r';
			}else {
				System.err.println ("Error in getChar");
				return (char)-1;
			}
		}else{
			return str.charAt(0);
		}
	}
	
	String getPPString(char c){
		if (c == 1) {
			return "\\1";
		}else if (c == 2) {
			return "\\2";
		}else if (c == 3) {
			return "\\3";
		}else if (c == 4) {
			return "\\4";
		}else if (c == 5) {
			return "\\5";
		}else if (c == 6) {
			return "\\6";
		}else if (c == 7) {
			return "\\7";
		}else if (c == 8) {
			return "\\8";
		}else if (c == '\\') {
			return "\\\\";
		}else if (c == ' ') {
			return "\\b";
		}else if (c == '\t') {
			return "\\t";
		}else if (c == '\n') {
			return "\\n";
		}else if (c == '\r') {
			return "\\r";
		}else {
			char s[] = {c};
			return new String(s);
		}
	}
	
	
	void test()
	{
		for(Enumeration e = transitionTable.keys();e.hasMoreElements();) {
			System.out.println ("Entry at "+ ((Integer)e.nextElement()).intValue());
		}
	}
	
	public void read(String file) throws Exception {
		//System.out.println("Reading automaton "+file);
		// Create buffered reader
		BufferedReader in = new BufferedReader(new FileReader(file));
		read(in);
	}
	
	public void read(BufferedReader in) throws Exception {
			//System.out.println("Reading automaton "+file);
		String str;
		Pattern transpat = 
			Pattern.compile("([0-9]+)\t([^\t]+):([^\t]+)\t([0-9]+)");
		Pattern startpat = 
			Pattern.compile("START:([^;]+);");
		Pattern endpat = 
			Pattern.compile("END: *([^;]+);");
		Pattern skippat = 
			Pattern.compile("TRANSITIONS:");
		Matcher m;
		while ((str = in.readLine()) != null) {
			m = transpat.matcher(str);
			if (m.matches()) {
				int st1 = Integer.parseInt(m.group(1),10);
				
				if (firstFreeState <= st1)
				{
					firstFreeState = st1 + 1;
				}
				char cin  = getChar(m.group(2));
				char cout  = getChar(m.group(3));
				int st2 = Integer.parseInt(m.group(4),10);
				if (firstFreeState <= st2)
				{
					firstFreeState = st2 + 1;
				}
				
				FSTEvent e = new FSTEvent(cin,cout);
				//e.print();
				addTransition(st1,e,st2);
				//System.out.println("Matching "+st1+", "+cin+", "+cout+", "+st2);
			}else{
				m = startpat.matcher(str);
				if (m.matches()) {
					String strstart[] = m.group(1).split(",",-1);
					for (int i = 0; i < strstart.length;i++){
						setStart(Integer.parseInt(strstart[i],10));
						//System.out.println("START: "+ strstart[i]);
					}
				}else{
					m = endpat.matcher(str);
					if (m.matches()) {
						String strend[] = m.group(1).split(",",-1);
						for (int i = 0; i < strend.length;i++){
							finalStates.add(new Integer (strend[i]));
							//System.out.println("END: "+ strend[i]);
						}
					}else{
						m = skippat.matcher(str);
						if (m.matches()) {
							//System.out.println("TRANSITIONS:");
						}else{
							System.err.println("Error matching "+str);
						}
					}
				}
			}
		}
		in.close();
	}
	
	public void write(String file) throws Exception {
		System.out.println("Writing: "+file);
		BufferedWriter out = new BufferedWriter(new FileWriter(file));
		write(out);
	}
	
	public void write(BufferedWriter out) throws Exception {
		String str;
		str = "START:"+getStart()+";";
		out.write(str,0,str.length());
		out.newLine();
		if (finalStates == null || finalStates.isEmpty()){
			//System.err.println("Automaton is void");
			Exception fstx = new Exception("Void Automaton");
			throw fstx;
		}
		Iterator ifs = finalStates.iterator();
		str = "END:"+ifs.next();
		System.out.print(str);
		out.write(str,0,str.length());
		while (ifs.hasNext()) {
			str = ","+ifs.next();
			out.write(str,0,str.length());
			System.out.print(str);
		}
		str = ";";
		out.write(str,0,str.length());
		System.out.println(str);
		out.newLine();
		str = "TRANSITIONS:";
		out.write(str,0,str.length());
		out.newLine();
		for(Enumeration e = transitionTable.keys();e.hasMoreElements();) {
			Integer is1 = (Integer)e.nextElement();
			for(FSTTransitionIterator it = getTransitions(is1);
			it.hasNext();){
				it.next();
				str = is1+"\t";
				str += getPPString(it.getEvent().getIn());
				str +=":";
				str += getPPString(it.getEvent().getOut());
				str +="\t";
				str += it.getState();
				out.write(str,0,str.length());
				out.newLine();
			}
		}

		out.close();
	}    
	
	public void print() throws Exception {
		String str;
		str = "START:"+getStart()+";";
		System.out.println(str);
		str = "END:";
		if (finalStates == null || finalStates.isEmpty()){
			Exception fstx = new Exception("Void Automaton");
			throw fstx;
		}
		Iterator ifs = finalStates.iterator();
		str += ifs.next();
		while (ifs.hasNext()) {
			str += ","+ifs.next();
		}
		str += ";";
		System.out.println(str);
		str = "TRANSITIONS:";
		System.out.println(str);
		for(Enumeration e = transitionTable.keys();e.hasMoreElements();) {
			Integer is1 = (Integer)e.nextElement();
			for(FSTTransitionIterator it = getTransitions(is1);
			it.hasNext();){
				it.next();
				str = is1+"\t";
				str += getPPString(it.getEvent().getIn());
				str +=":";
				str += getPPString(it.getEvent().getOut());
				str +="\t";
				str += it.getState();
				System.out.println(str);
			}
		}
	}    
	
	public FSTAutomaton anyAny () {
		int s = 1;start = s;
		
		addTransition(s,new FSTEvent((char)2,(char)2),s+1);
		s++;
		insertFinalState(s);
		setFirstFreeState(s+1);
		return this;
	}	 
	
	public FSTAutomaton epsilonStr (String str) {
		int s = 1;start = s;
		
		for (int i = 0; i < str.length(); i++)
		{
			addTransition(s,new FSTEvent((char)1,str.charAt(i)),s+1);
			s++;
		}
		insertFinalState(s);
		setFirstFreeState(s+1);
		return this;
	}	 
	
	public FSTAutomaton strEpsilon (String str) {
		int s = 1;start = s;
		
		for (int i = 0; i < str.length(); i++)
		{
			addTransition(s,new FSTEvent(str.charAt(i),(char)1),s+1);
			s++;
		}
		insertFinalState(s);
		setFirstFreeState(s+1);
		return this;
	}	 
	
	public FSTAutomaton strId (String str) {
		int s = 1;start = s;
		
		for (int i = 0; i < str.length(); i++)
		{
			addTransition(s,new FSTEvent(str.charAt(i),str.charAt(i)),s+1);
			s++;
		}
		insertFinalState(s);
		setFirstFreeState(s+1);
		return this;
	}	 
	
	public void trieInsertStr (String str) {
		FSTEvent e;
		int s1 = 1;
		int s2,i;
		// FSTState s3;
		s2 = 1;
		start = 1;
		e = null;
		for (i = 0; s1 != 0 && i < str.length(); i++)
		{
			s2 = s1;
			e = new FSTEvent(str.charAt(i),str.charAt(i));
			s1 = nextState(s1,e);
		}
		
		if (s1 != 0)
		{
			insertFinalState(s1);
		}
		else
		{
			s1 = s2;
			s2 = getFirstFreeState ();
			addTransition (s1,e,s2);
			for (;i < str.length(); i++)
			{
				e = new FSTEvent(str.charAt(i),str.charAt(i));
				addTransition (s2,e,s2+1);
				s2++;
			}
			insertFinalState(s2);
			setFirstFreeState(s2+1);
		}
	}
	
	
}
